package es.upm.gib.mappingAPI2;

import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * This class could transform a MappingApi2 object to XML file or string and vice versa
 */
public class FormatEncoder implements Serializable {

    //=======================================================================
    //ATTRIBUTES
    //=======================================================================

    /*mapping api to write or to read*/
    private MappingApi2 api;

    /*File to write or file to read*/
    private File file;

    /*External bounds of the mapping*/
    Map<String, List<String>> externalBounds;

    //=======================================================================
    // CONSTRUCTORS
    //=======================================================================

    /**
     * Constructor
     * @param api2 MappingApi object
     * @param file File object
     */
    public FormatEncoder(MappingApi2 api2, File file) {
        this.api = api2;
        this.file = file;
        this.externalBounds = new HashMap<String, List<String>>();
        //exampleJdom();
    }

    /**
     * Constructor
     */
    public FormatEncoder() {
        this.api = new MappingApi2();
    }


    //=======================================================================
    // PUBLIC METHODS
    //=======================================================================


    /**
     * This method add string content to an input element
     *
     * @param element element
     * @param value   content to add
     * @param label   label of the element
     * @return the element
     */
    private Element addContent(Element element, String value, String label) {
        if ((value != null)) {
            Element data = new Element(label);
            data.setText(value);
            element.addContent(data);
        }
        return element;
    }

    /**
     * This method adds a Restriction to the Path in mapping
     *
     * @param att The String of restriction (operator##operand)
     * @param ob   content to add
     * @return The Restriction created from the Attribute data
     */
    private Restriction addRestriction(Attribute att, Element ob){
        String[] split = att.getValue().split("##");
        Restriction.Operator operator = null;
        Restriction.Range range;
        for(Restriction.Operator op : Restriction.Operator.values()){
            if(split[0].equals(op.name())){
                operator = op;
                break;
            }
        }
        String sRange = ob.getText().substring(ob.getText().indexOf("#")+1);
        if(sRange.equals("string"))
            range = Restriction.Range.STRING;
        else if(sRange.equals("integer"))
            range = Restriction.Range.INTEGER;
        else if(sRange.equals("float"))
            range = Restriction.Range.FLOAT;
        else
            range = Restriction.Range.INSTANCE;

        return new Restriction(operator,split[1],range);
    }

    /**
     * This method save all the database information of the mapping to the metadata element
     *
     * @param metadata     metadata element
     * @param databaseInfo database information
     * @return metadata element
     */
    private Element saveDataBaseInfo(Element metadata, DatabaseInfo databaseInfo) {
        if (databaseInfo != null) {
            Element dataBase = new Element("data_base_info");
            dataBase = addContent(dataBase, databaseInfo.getDbName(), "db_name");
            dataBase = addContent(dataBase, databaseInfo.getDbDescription(), "db_description");
            dataBase = addContent(dataBase, databaseInfo.getDbUrl(), "db_url");
            dataBase = addContent(dataBase, databaseInfo.getDbId(), "db_id");
            dataBase = addContent(dataBase, databaseInfo.getDbUsr(), "db_usr");
            dataBase = addContent(dataBase, databaseInfo.getDbPsw(), "db_psw");
            metadata.addContent(dataBase);
        }
        return metadata;
    }

    /**
     * This method save the list of authors to the metadata element
     *
     * @param authors  List of authors to set to the metadata
     * @param metadata metadata element
     * @return metadata element
     */
    private Element saveAuthors(Set<String> authors, Element metadata) {

        if (!authors.isEmpty()) {
            Element authorsList = new Element("authors");
            for (String author : authors) {
                authorsList = addContent(authorsList, author, "author");
            }
            metadata.addContent(authorsList);
        }
        return metadata;
    }

    /**
     * This method add content of date to an input element
     *
     * @param element input element
     * @param value   date
     * @param label   label of the element
     * @return the element
     */
    private Element saveDateInfo(Element element, Date value, String label) {
        if (value != null) {
            String date = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.ENGLISH).format(value);
            Element data = new Element(label);
            data.setText(date);
            element.addContent(data);
        }
        return element;
    }

    /**
     * This method add a map of elements to the metadata element
     *
     * @param map       map of elements to add
     * @param metadata  metadata element
     * @param dataSt    label of the element
     * @param elementSt statement of the element
     * @return metadata element
     */
    private Element saveList(Map<String, String> map, Element metadata, String dataSt, String elementSt) {
        if (!map.isEmpty()) {
            Iterator it = map.entrySet().iterator();
            if (it.hasNext()) {
                Element data = new Element(dataSt);
                while (it.hasNext()) {
                    Element element = new Element(elementSt);
                    Map.Entry e = (Map.Entry) it.next();
                    String index = (String) e.getKey();
                    String value = (String) e.getValue();
                    Element in = new Element("index");
                    Element val = new Element("value");
                    in.setText(index);
                    val.setText(value);
                    element.addContent(in);
                    element.addContent(val);
                    data.addContent(element);
                }
                metadata.addContent(data);
            }
        }
        return metadata;
    }

    /**
     * This method save all content of the metadata
     *
     * @param metadata metadata element
     * @return metadata element
     */
    private Element saveMetadata(Element metadata) {
        MappingMetadata mappingMetadata = api.getMetadata();
        if (mappingMetadata != null) {
            metadata = addContent(metadata, mappingMetadata.getMappingFormatVersion(), "mapping_format_version");
            metadata = addContent(metadata, mappingMetadata.getMappingName(), "mapping_name");
            metadata = addContent(metadata, mappingMetadata.getDescription(), "description");
            metadata = saveDataBaseInfo(metadata, mappingMetadata.getDatabaseInfo());
            metadata = saveAuthors(mappingMetadata.getAuthors(), metadata);
            metadata = saveDateInfo(metadata, mappingMetadata.getCreationDate(), "creation_date");
            metadata = addContent(metadata, mappingMetadata.getLastModifiedBy(), "last_modified_by");
            metadata = saveDateInfo(metadata, mappingMetadata.getLastModificationDate(), "last_modification_date");
            metadata = saveList(mappingMetadata.getAbbreviationList(), metadata, "abbreviation_list", "abbreviation");
            metadata = saveList(mappingMetadata.getCustomData().getData(), metadata, "custom_data", "row");
        }
        return metadata;
    }

    /**
     * This method returns an entry element with an input view actualized
     *
     * @param type  if type == 0 then the view is the physical one, if it is 1 the view is the conceptual
     * @param e     Entry to add the view
     * @param entry entry element
     * @return entry element
     */
    private Element addView(int type, Entry e, Element entry) {
        ArrayList<Path> pathList = new ArrayList<Path>();
        String view = "";
        ArrayList<CoordinatesBound> internal = new ArrayList<CoordinatesBound>();
        ArrayList<CoordinatesBound> external = e.getExternalBounds();
        Map<String, String> externalBoundss = new HashMap<String, String>();
        ArrayList<Identifier> identifiers = new ArrayList<Identifier>();
        if (type == 0) {
            pathList = e.getPhysicalView().getPaths();
            view = "physical_view";
            internal = e.getPhysicalView().getInternalBounds();
            externalBoundss = getPhysicalExternalBounds(external);
            identifiers = e.getPhysicalView().getIdentifiers();
        } else if (type == 1) {
            pathList = e.getConceptualView().getPaths();
            view = "conceptual_view";
            internal = e.getConceptualView().getInternalBounds();
            externalBoundss = getPhysicalExternalBounds(external);
            externalBoundss = getConceptualExternalBounds(externalBoundss, external);
            identifiers = e.getConceptualView().getIdentifiers();
        }
        Map<String, String> mapBounds = getLabels(internal);
        Map<String, String> mapIds = getIds(identifiers);
        Element physicalView = new Element(view);
        Element paths = new Element("paths");
        for (int pos = 0; pos < pathList.size(); pos++) {
            Path path = pathList.get(pos);
            Element dataPath = new Element("path");
            boolean first = true;
            for (int i = 0; i < path.size(); i++) {
                Triple triple = path.get(i);
                Restriction restriction = path.getRestrictions(0);
                if (first) {
                    Element class_ = new Element("class");
                    if (externalBoundss.containsKey("" + pos + "," + i)) {
                        Attribute attribute = new Attribute("externalBound", externalBoundss.get("" + pos + "," + i));
                        class_.setAttribute(attribute);
                    }
                    if (mapBounds.containsKey("" + pos + "," + i)) {
                        Attribute attribute = new Attribute("internalBound", mapBounds.get("" + pos + "," + i));
                        class_.setAttribute(attribute);
                    }
                    if (mapIds.containsKey("" + pos + "," + i)) {
                        Attribute attribute = new Attribute("identifier", mapIds.get("" + pos + "," + i));
                        class_.setAttribute(attribute);
                    }
                    if (restriction!=null){
                        Attribute attribute = new Attribute("restriction",restriction.getOp()+"##"+restriction.getValue());
                        class_.setAttribute(attribute);
                    }
                    class_.setText(solveName(triple.getOrigin()));
                    dataPath.addContent(class_);
                    first = false;
                }
                Element rel = new Element("property");
                rel.setText(solveName(triple.getRelation()));
                dataPath.addContent(rel);
                Element dest = new Element("class");
                restriction = path.getRestrictions(i+1);
                if (externalBoundss.containsKey("" + pos + "," + (i + 1))) {
                    Attribute attribute = new Attribute("externalBound", externalBoundss.get("" + pos + "," + (i + 1)));
                    dest.setAttribute(attribute);
                }
                if (mapBounds.containsKey("" + pos + "," + (i + 1))) {
                    Attribute attribute = new Attribute("internalBound", mapBounds.get("" + pos + "," + (i + 1)));
                    dest.setAttribute(attribute);
                }
                if (mapIds.containsKey("" + pos + "," + (i + 1))) {
                    Attribute attribute = new Attribute("identifier", mapIds.get("" + pos + "," + (i + 1)));
                    dest.setAttribute(attribute);
                }
                if (restriction != null){
                    Attribute attribute = new Attribute("restriction",restriction.getOp()+"##"+restriction.getValue());
                    dest.setAttribute(attribute);
                }

                dest.setText(solveName(triple.getDestination()));
                dataPath.addContent(dest);
            }
            paths.addContent(dataPath);
        }
        physicalView.addContent(paths);

        entry.addContent(physicalView);
        return entry;
    }

    /**
     * This method add an entry to the mapping
     *
     * @param entry entry element
     * @param e     Entry to add
     * @return entry element
     */
    public Element addEntry(Element entry, Entry e) {
        entry = addContent(entry, e.getDescription(), "description");
        entry = addContent(entry, e.getAuthor(), "author");
        entry = saveDateInfo(entry, e.getCreationDate(), "creation_date");
        entry = addContent(entry, e.getLastModifiedBy(), "last_modified_by");
        entry = saveDateInfo(entry, e.getLastModificationDate(), "last_modification_date");
        entry = saveList(e.getCustomData().getData(), entry, "custom_data", "row");
        entry = addView(0, e, entry);
        entry = addView(1, e, entry);
        return entry;
    }

    /**
     * This method read an XML file from an input string or an input file
     *
     * @param i i==0 for a file, i==1 for string
     * @return final string
     */
    private String createXML(int i) {
        //i = 0; create XML and put it in a file
        //i = 1; create XML and parse it to a string

        Element mapping = new Element("mapping");
        Document doc = new Document(mapping);
        String finalString = "";
        Element metadata = new Element("metadata");
        metadata = saveMetadata(metadata);
        doc.getRootElement().addContent(metadata);
        ArrayList<Entry> entries = api.getEntries();
        for (Entry e : entries) {
            Element entry = new Element("entry");
            entry = addEntry(entry, e);
            doc.getRootElement().addContent(entry);
        }
        XMLOutputter xmlOutput = new XMLOutputter();
        xmlOutput.setFormat(Format.getPrettyFormat());
        if (i == 0) {
            try {
                xmlOutput.output(doc, new FileWriter(file));
            } catch (IOException io) {
                System.out.println(io.getMessage());
            }
        } else {
            finalString = xmlOutput.outputString(doc);
        }
        return finalString;

    }

    /**
     * This method returns the mapping in XML format through one string
     *
     * @return XML string
     */
    public String stringXML() {
        return createXML(1);
    }

    /**
     * This method create the XML file of the model
     */
    public void submitXML() {
        createXML(0);
    }

    /**
     * This method returns list of external bounds from physical view
     *
     * @param bounds external bounds
     * @return external bounds from physical view
     */
    private Map<String, String> getPhysicalExternalBounds(ArrayList<CoordinatesBound> bounds) {
        Map<String, String> boundStringMap = new HashMap<String, String>();
        int i = 1;
        for (CoordinatesBound bound : bounds) {
            String key = "" + bound.getFirstPath() + "," + bound.getFirstClass();
            if (!boundStringMap.containsKey(key)) {
                boundStringMap.put(key, "ExternalBound" + i);
                i++;
            }
        }
        return boundStringMap;
    }

    /**
     * This method checks that external bounds of conceptual view are the same of physical and returns a map of these external bounds
     *
     * @param boundsMap external bounds of physical view
     * @param bounds    external bounds of conceptual view
     * @return external bounds map
     */
    private Map<String, String> getConceptualExternalBounds(Map<String, String> boundsMap, ArrayList<CoordinatesBound> bounds) {
        Map<String, String> finalMap = new HashMap<String, String>();
        for (CoordinatesBound bound : bounds) {
            String key = "" + bound.getFirstPath() + "," + bound.getFirstClass();
            String key2 = "" + bound.getSecondPath() + "," + bound.getSecondClass();
            if (boundsMap.containsKey(key)) {
                finalMap.put(key2, boundsMap.get(key));
            }
        }
        return finalMap;
    }

    /**
     * This method creates a map with the name of the identifying classes and its coordinates
     * @param identifiers identifier classes
     * @return  a map with the name of the identifying classes and its coordinates
     */
    private Map<String, String> getIds(ArrayList<Identifier> identifiers) {
        Map<String, String> identifiersMap = new HashMap<String, String>();
        for (Identifier id : identifiers) {
            String key = "" + String.valueOf(id.getPath()) + "," + String.valueOf(id.getClass_());
            if (!identifiersMap.containsKey(key)) {
                identifiersMap.put(key, "YES");
            }
        }
        return identifiersMap;
    }

    /**
     * This method retrieves a map of bounds
     * @param bounds bounds
     * @return map of bounds
     */
    private Map<String, String> getLabels(ArrayList<CoordinatesBound> bounds) {
        Map<String, String> boundStringMap = new HashMap<String, String>();
        int i = 1;
        for (CoordinatesBound bound : bounds) {
            String key = "" + bound.getFirstPath() + "," + bound.getFirstClass();
            String key2 = "" + bound.getSecondPath() + "," + bound.getSecondClass();
            if (!boundStringMap.containsKey(key)) {
                if (!boundStringMap.containsKey(key2)) {
                    boundStringMap.put(key, "InternalBound" + i);
                    boundStringMap.put(key2, "InternalBound" + i);
                    i++;
                } else {
                    boundStringMap.put(key, boundStringMap.get(key2));
                }
            } else if (!boundStringMap.containsKey(key2)) {
                boundStringMap.put(key2, boundStringMap.get(key));
            }
        }
        return boundStringMap;
    }


    /**
     * This method retrieves a string without " "
     * @param name name to solve
     * @return a string without " "
     */
    private String solveName(String name) {
        String result = "";
        if (name.charAt(0) == '"') {
            int pos = 1;
            while ((name.charAt(pos) != '"') && (pos < name.length())) {
                result = result + name.charAt(pos);
                pos++;
            }
            return result;
        } else {
            return name;
        }
    }


    /**
     * This method read an XML file and create the mapping api model from it
     *
     * @param path path of the file to read
     * @return the mappingApi2 object
     * @throws JDOMException if it is not able to parse the file with JDOM
     * @throws IOException JDOM can not build the file to create a jdom.Document
     * @throws ParseException The project is not able to parse a date of the XML
     */
    public MappingApi2 readFromXML(String path) throws JDOMException, IOException, ParseException {
        return readXML(path, 0);
    }

    /**
     * This method read a string in XML format and create the mapping api model from it
     *
     * @param model String to parse as a MappingApi2 object
     * @return the MappingApi2 object created through the input String
     * @throws JDOMException The String can not be parsed with JDOM
     * @throws IOException JDOM can not build the String to create a jdom.Document
     * @throws ParseException The project is not able to parse a date of the XML
     */
    public MappingApi2 readFromString(String model) throws JDOMException, IOException, ParseException {
        return readXML(model, 1);
    }

    /**
     * This method set the information of the element data into a MappingMetadata object
     *
     * @param data data element
     * @return MappingMetadata object
     * @throws ParseException The project is not able to parse a date of the XML
     */
    private MappingMetadata readMetadata(Element data) throws ParseException {
        MappingMetadata metadata = new MappingMetadata();
        List content = data.getChildren();
        for (int j = 0; j < content.size(); j++) {
            Element element = (Element) content.get(j);
            if (element.getName().equals("mapping_format_version")) {
                metadata.setMappingFormatVersion(element.getText());
            } else if (element.getName().equals("mapping_name")) {
                metadata.setMappingName(element.getText());
            } else if (element.getName().equals("description")) {
                metadata.setDescription(element.getText());
            } else if (element.getName().equals("data_base_info")) {
                DatabaseInfo databaseInfo = new DatabaseInfo();
                List att = element.getChildren();
                for (int pos = 0; pos < att.size(); pos++) {
                    Element el = (Element) att.get(pos);
                    if (el.getName().equals("db_name")) {
                        databaseInfo.setDbName(el.getText());
                    } else if (el.getName().equals("db_description")) {
                        databaseInfo.setDbDescription(el.getText());
                    } else if (el.getName().equals("db_url")) {
                        databaseInfo.setDbUrl(el.getText());
                    } else if (el.getName().equals("db_id")) {
                        databaseInfo.setDbId(el.getText());
                    } else if (el.getName().equals("db_usr")) {
                        databaseInfo.setDbUsr(el.getText());
                    } else if (el.getName().equals("db_psw")) {
                        databaseInfo.setDbPsw(el.getText());
                    }
                }
                metadata.setDatabaseInfo(databaseInfo);
            } else if (element.getName().equals("authors")) {
                List authors = element.getChildren();
                Set<String> authorsOf = new HashSet<String>();
                for (int pos = 0; pos < authors.size(); pos++) {
                    Element el = (Element) authors.get(pos);
                    authorsOf.add(el.getText());
                }
                metadata.setAuthors(authorsOf);
            } else if (element.getName().equals("creation_date")) {
                String text = element.getText();
                SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.ENGLISH);
                Date date = format.parse(text);
                metadata.setCreationDate(date);
                //metadata.setCreationDate(element.getText().);
            } else if (element.getName().equals("last_modified_by")) {
                metadata.setLastModifiedBy(element.getText());
            } else if (element.getName().equals("last_modification_date")) {
                String text = element.getText();
                SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.ENGLISH);
                Date date = format.parse(text);
                metadata.setLastModificationDate(date);
                //metadata.setDescription(element.getText());
            } else if (element.getName().equals("abbreviation_list")) {
                List abbr = element.getChildren();
                Map<String, String> abbrList = new HashMap<String, String>();
                for (int pos = 0; pos < abbr.size(); pos++) {
                    Element el = (Element) abbr.get(pos);
                    String index = "";
                    String value = "";
                    List contentOf = el.getChildren();
                    for (int in = 0; in < contentOf.size(); in++) {
                        Element elementOf = (Element) contentOf.get(in);
                        if (elementOf.getName().equals("index")) {

                            index = elementOf.getText();
                        } else if (elementOf.getName().equals("value")) {

                            value = elementOf.getText();
                        }
                    }
                    abbrList.put(index, value);
                }
                metadata.setAbbreviationList(abbrList);
            } else if (element.getName().equals("custom_data")) {
                List custom = element.getChildren();
                Map<String, String> customData = new HashMap<String, String>();
                for (int pos = 0; pos < custom.size(); pos++) {
                    Element c = (Element) custom.get(pos);
                    if (c.getName().equals("row")) {
                        String index = "";
                        String value = "";
                        for (int r = 0; r < c.getChildren().size(); r++) {
                            Element e = (Element) c.getChildren().get(r);
                            if (e.getName().equals("index")) {
                                index = e.getText();
                            } else if (e.getName().equals("value")) {
                                value = e.getText();
                            }
                        }
                        customData.put(index, value);
                    }
                }
                if (customData.size() > 0) {
                    CustomData customData1 = new CustomData();
                    customData1.setData(customData);
                    metadata.setCustomData(customData1);
                }
            }
        }
        return metadata;
    }

    /**
     * This method set date data of an element to a date object
     *
     * @param element data element
     * @return date object
     * @throws ParseException The project is not able to parse a date of the XML
     */
    private Date setDateElement(Element element) throws ParseException {
        String text = element.getText();
        SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.ENGLISH);
        return format.parse(text);
    }

    /**
     * This method set  custom data from an input element
     *
     * @param element input element
     * @return custom data
     */
    private CustomData setCustomData(Element element) {
        CustomData customData = new CustomData();
        List custom = element.getChildren();
        Map<String, String> customDataMap = new HashMap<String, String>();
        for (Object aCustom : custom) {
            Element c = (Element) aCustom;
            if (c.getName().equals("row")) {
                String index = "";
                String value = "";
                for (int r = 0; r < c.getChildren().size(); r++) {
                    Element e = (Element) c.getChildren().get(r);
                    if (e.getName().equals("index")) {
                        index = e.getText();
                    } else if (e.getName().equals("value")) {
                        value = e.getText();
                    }
                }
                customDataMap.put(index, value);
            }
        }
        if (customDataMap.size() > 0) {
            customData.setData(customDataMap);
        }
        return customData;
    }


    /**
     * This method returns Map of bounds to add it to the model
     *
     * @param bounds bounds to add
     * @param att attribute
     * @param pathCount number of path
     * @param tripleCount position of triple
     * @return Map of bounds to add it to the model
     */
    private Map<String, List<String>> setBounds(Map<String, List<String>> bounds, Attribute att, int pathCount, int tripleCount) {
        String value = att.getValue();
        if (!bounds.containsKey(value)) {
            List<String> coordinatesBounds = new ArrayList<String>();
            coordinatesBounds.add("" + pathCount + "," + tripleCount);
            bounds.put(value, coordinatesBounds);
        } else {
            List<String> coordinatesBounds = bounds.get(value);
            String coord = "" + pathCount + "," + (tripleCount);
            coordinatesBounds.add(coord);
        }
        return bounds;
    }

    /**
     * This method create an object view from the input element and returns it
     *
     * @param element input element
     * @return view object
     */
    private View setView(Element element) {
        List viewElements = element.getChildren();
        View view = new View();
        //externalBounds = new HashMap<String, List<String>>();
        Map<String, List<String>> internalBounds = new HashMap<String, List<String>>();
        ArrayList<Identifier> identifiers = new ArrayList<Identifier>();
        for (int pos = 0; pos < viewElements.size(); pos++) {
            Element data = (Element) viewElements.get(pos);
            if (data.getName().equals("paths")) {
                List paths = data.getChildren();
                ArrayList<Path> finalPaths = new ArrayList<Path>();
                int pathCount = 0;
                for (Object pathr : paths) {
                    Element dataPath = (Element) pathr;
                    Map<Integer,Restriction> restriction = new HashMap<Integer, Restriction>();
                    if (dataPath.getName().equals("path")) {
                        List pathElements = dataPath.getChildren();
                        int t = 0;
                        String class1 = "";
                        String prop = "";
                        String class2 = "";
                        ArrayList<Triple> triples = new ArrayList<Triple>();
                        int tripleCount = 0;
                        while ((t < pathElements.size())) {
                            Element ob = (Element) pathElements.get(t);
                            if (ob.getName().equals("class")) {
                                List atts = ob.getAttributes();
                                for (Object at : atts) {
                                    Attribute att = (Attribute) at;
                                    if (att.getName().equals("internalBound")) {
                                        internalBounds = setBounds(internalBounds, att, pathCount, tripleCount);
                                    } else if (att.getName().equals("externalBound")) {
                                        externalBounds = setBounds(externalBounds, att, pathCount, tripleCount);
                                    }
                                    if (att.getName().equals("identifier")) {
                                        Identifier identifier = new Identifier(pathCount, tripleCount);
                                        if (!identifiers.contains(identifier)) {
                                            identifiers.add(identifier);
                                        }
                                    }
                                    if(att.getName().equals("restriction")){
                                        restriction.put(tripleCount,addRestriction(att,ob));
                                    }
                                }
                                class1 = ob.getText();
                                t++;
                                if (t < pathElements.size()) {
                                    ob = (Element) pathElements.get(t);
                                    if (ob.getName().equals("property")) {
                                        prop = ob.getText();
                                        t++;
                                        if (t < pathElements.size()) {
                                            ob = (Element) pathElements.get(t);
                                            if (ob.getName().equals("class")) {
                                                atts = ob.getAttributes();
                                                for (Object at : atts) {
                                                    Attribute att = (Attribute) at;
                                                    if (att.getName().equals("internalBound")) {
                                                        internalBounds = setBounds(internalBounds, att, pathCount, (tripleCount + 1));
                                                    } else if (att.getName().equals("externalBound")) {
                                                        externalBounds = setBounds(externalBounds, att, pathCount, (tripleCount + 1));
                                                    }
                                                    if (att.getName().equals("identifier")) {
                                                        Identifier identifier = new Identifier(pathCount, tripleCount + 1);
                                                        if (!identifiers.contains(identifier)) {
                                                            identifiers.add(identifier);
                                                        }
                                                    }
                                                    if(att.getName().equals("restriction")){
                                                        restriction.put(tripleCount+1,addRestriction(att,ob));
                                                    }
                                                }
                                                class2 = ob.getText();
                                                t++;
                                                Triple triple = new Triple(class1, prop, class2);
                                                triples.add(triple);
                                                tripleCount++;
                                            }
                                        }
                                    }
                                }
                            }else if (ob.getName().equals("property")) {
                                class1 = class2;
                                prop = ob.getText();
                                t++;
                                if (t < pathElements.size()) {
                                    ob = (Element) pathElements.get(t);
                                    if (ob.getName().equals("class")) {
                                        List atts = ob.getAttributes();
                                        for (Object at : atts) {
                                            Attribute att = (Attribute) at;
                                            if (att.getName().equals("internalBound")) {
                                                internalBounds = setBounds(internalBounds, att, pathCount, (tripleCount + 1));
                                            } else if (att.getName().equals("externalBound")) {
                                                externalBounds = setBounds(externalBounds, att, pathCount, (tripleCount + 1));
                                            }
                                            if (att.getName().equals("identifier")) {
                                                Identifier identifier = new Identifier(pathCount, tripleCount + 1);
                                                if (!identifiers.contains(identifier)) {
                                                    identifiers.add(identifier);
                                                }
                                            }
                                            if(att.getName().equals("restriction")){
                                                restriction.put(tripleCount+1,addRestriction(att,ob));
                                            }
                                        }
                                        class2 = ob.getText();
                                        t++;
                                        Triple triple = new Triple(class1, prop, class2);
                                        triples.add(triple);
                                        tripleCount++;
                                    }
                                }
                            }

                        }
                        Path newPath = new Path(triples);
                        for (Map.Entry<Integer, Restriction> integerRestrictionEntry : restriction.entrySet()) {
                            Map.Entry ent = (Map.Entry) integerRestrictionEntry;
                            newPath.setRestriction((Integer) ent.getKey(), (Restriction) ent.getValue());
                        }

                        finalPaths.add(newPath);
                    }
                    pathCount++;
                }
                view = new View(finalPaths);
                ArrayList<CoordinatesBound> internal = solveBounds(internalBounds);
                view.setInternalBounds(internal);
                for (Identifier id : identifiers) {
                    view.addIdentifier(id);
                }
            }
        }
        return view;
    }


    /**
     * This method create entry object from the input element data and returns it
     *
     * @param data  data element
     * @param entry entry to read
     * @return entry object
     * @throws ParseException The project is not able to parse a date of the XML
     */
    private Entry readEntry(Element data, Entry entry) throws ParseException {
        List content = data.getChildren();
        externalBounds = new HashMap<String, List<String>>();
        for (int j = 0; j < content.size(); j++) {
            Element element = (Element) content.get(j);
            if (element.getName().equals("description")) {
                entry.setDescription(element.getText());
            } else if (element.getName().equals("author")) {
                entry.setAuthor(element.getText());
            } else if (element.getName().equals("creation_date")) {
                entry.setCreationDate(setDateElement(element));
            } else if (element.getName().equals("last_modified_by")) {
                entry.setLastModifiedBy(element.getText());
            } else if (element.getName().equals("last_modification_date")) {
                entry.setLastModificationDate(setDateElement(element));
            } else if (element.getName().equals("custom_data")) {
                entry.setCustomData(setCustomData(element));
            } else if (element.getName().equals("physical_view")) {
                entry.setPhysicalView(setView(element));
            } else if (element.getName().equals("conceptual_view")) {
                entry.setConceptualView(setView(element));
            }

        }
        ArrayList<CoordinatesBound> external = solveBounds(externalBounds);
        entry.setExternalBounds(external);
        return entry;
    }

    /**
     * This method creates a MappingApi2 object. If k==0 path is the path of a file, if k==1 path is a string in XML format
     *
     * @param path if K==0 path of the file. If K==1 String to read
     * @param k K==0 if the parameter path is a file, K==1 if the parameter path is String
     * @return MappingAPi2 object created
     * @throws JDOMException The String can not be parsed with JDOM
     * @throws IOException JDOM can not build the String to create a jdom.Document
     * @throws ParseException The project is not able to parse a date of the XML
     */
    private MappingApi2 readXML(String path, int k) throws JDOMException, ParseException, IOException {
        SAXBuilder builder = new SAXBuilder();
        Document doc = null;
        if (k == 0) {
            File file = new File(path);
            doc = builder.build(file);
        } else {
            Reader reader = new StringReader(path);
            doc = builder.build(reader);
        }
        MappingApi2 map = new MappingApi2();
        Element rootNode = doc.getRootElement();
        List children = rootNode.getChildren();
        for (int i = 0; i < children.size(); i++) {
            Element data = (Element) children.get(i);
            String name = data.getName();
            if (name.equals("metadata")) {
                MappingMetadata metadata = readMetadata(data);
                map.setMetadata(metadata);
            } else if (name.equals("entry")) {
                Entry entry = new Entry();
                entry = readEntry(data, entry);
                map.addEntry(entry);
            }
        }
        return map;
    }

    /**
     * This method retrieves the bounds
     * @param bounds bounds to solve
     * @return bounds
     */
    private ArrayList<CoordinatesBound> solveBounds(Map<String, List<String>> bounds) {
        ArrayList<CoordinatesBound> finalBounds = new ArrayList<CoordinatesBound>();
        Iterator it = bounds.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry e = (Map.Entry) it.next();
            List<String> values = (List<String>) e.getValue();
            if (!values.isEmpty()) {
                String first = values.get(0);
                String firstPath = "";
                int pos = 0;
                while ((first.charAt(pos) != ',') && (pos < first.length())) {
                    firstPath = firstPath + first.charAt(pos);
                    pos++;
                }
                pos++;
                String firstClass = "";
                while (pos < first.length()) {
                    firstClass = firstClass + first.charAt(pos);
                    pos++;
                }
                for (int i = 1; i < values.size(); i++) {
                    String data = values.get(i);
                    String secondPath = "";
                    pos = 0;
                    while ((data.charAt(pos) != ',') && (pos < data.length())) {
                        secondPath = secondPath + data.charAt(pos);
                        pos++;
                    }
                    pos++;
                    String secondClass = "";
                    while (pos < data.length()) {
                        secondClass = secondClass + data.charAt(pos);
                        pos++;
                    }
                    CoordinatesBound coordinatesBound = new CoordinatesBound(Integer.parseInt(firstPath), Integer.parseInt(firstClass), Integer.parseInt(secondPath), Integer.parseInt(secondClass));
                    finalBounds.add(coordinatesBound);
                }
            }
        }
        return finalBounds;
    }
}
